//
// @Author: Zhenwei Xiao
// @Description: 身份定义
// @Version: 1.0.0
// @Date: 2021/3/5 上午5:48
// @Copyright: MIN-Group；国家重大科技基础设施——未来网络北大实验室；深圳市信息论与未来网络重点实验室
//

const com = require("../common")
const uti = require("../crypto/utils")
const hashfun = require("../crypto")
const sm2 = require("../crypto/sm2/external")
const sm4 = require("sm-crypto").sm4;
const sec = require("../crypto")
const cer = require("../crypto/cert/cert");

//身份采用的密码学算法
class KeyParam {
    constructor(PublicKeyAlgorithm, SignatureAlgorithm){
        this.PublicKeyAlgorithm = PublicKeyAlgorithm 
        this.SignatureAlgorithm = SignatureAlgorithm
    }
}

class Identity {
    constructor(Name, KeyParam, Prikey, PrikeyRawByte, Pubkey, Passwd, Cert){
        this.Name = Name                         //用户名
        this.KeyParam = KeyParam                 //身份采用的密码学算法
        this.Prikey = Prikey                     //私钥
        this.PrikeyRawByte =  PrikeyRawByte      //加密后的私钥
        this.Pubkey = Pubkey                     //公钥
        this.Passwd = Passwd                     //密码
        this.Cert = Cert                         //证书
    }
	// Name          string           //用户名
	// KeyParam      KeyParam         //身份采用的密码学算法
	// Prikey        sec.PrivateKey   //私钥
	// PrikeyRawByte []byte           //加密后的私钥
	// Pubkey        sec.PublicKey    //公钥
	// Passwd        string           //密码
	// Cert          cert.Certificate //证书
    
    //
    // 用户名密码标准化输出
    //
    // @Description:
    // @return string
    //
    String() {
        var s = "{Name:%s,Passwd:%s}"
        return s.format(this.Name, this.Passwd)
    }
    
    //
    // 使用Identity对象进行签名
    //
    // @Description:
    // @receiver id
    // @param rand io.Reader
    // @param digest []byte
    // @param opts sec.SignerOpts
    // @return []byte
    // @return error
    //
    Sign(digest, opts) {
        if (this.Prikey == null) {
            return [null, new Error("Invalid Prikey")]
        }
        switch (this.KeyParam.PublicKeyAlgorithm) {
        case com.PublicKeyAlgorithm.SM2:
            var p
            try{
                p = new sm2.Sm2PrivateKey(this.Prikey.PrivateKey)
            }catch(err){
                return [null, new Error("Invalid Prikey")]
            }
            switch (this.KeyParam.SignatureAlgorithm) {
            case com.SignatureAlgorithm.SM2WithSM3:
                return p.Sign(uti.Bytes2Str(digest), null)
            default:
                return [null, new Error("Can't find the sign algorithm")]
            }
        }

        return [null, new Error("Can't find key type")]
    }

    //
    // 使用Identify对象进行签名验证
    //
    // @Description:
    // @receiver id
    // @param msg []byte
    // @param sign []byte
    // @param opts sec.SignerOpts
    // @return bool
    // @return error
    //
    Verify(msg, sign, opts) {
        if (this.Pubkey == null) {
            return [false, new Error("Invalid Pubkey")]
        }
        switch (this.KeyParam.PublicKeyAlgorithm) {
        case com.PublicKeyAlgorithm.SM2:
            var p
            try{
                p = new sm2.Sm2PublicKey(this.Pubkey.PublicKey)
            }catch(err){
                return [null, new Error("Invalid Prikey")]
            }
            switch (this.KeyParam.SignatureAlgorithm) {
            case com.SignatureAlgorithm.SM2WithSM3:
                return p.Verify(uti.Bytes2Str(msg), uti.Bytes2Str(sign), opts)
            default:
                return [false, new Error("Can't find the sign algorithm")]
            }
        }

        return [false, new Error("Can't find key type")]
    }

    //
    // 使用Identity进行私钥解密
    //
    // @Description:
    // @receiver id
    // @param rand io.Reader
    // @param msg  []byte
    // @param opts  sec.DecrypterOpts
    // @return []byte
    // @return error
    //
    AsymDecrypt(msg, opts) {
        if (this.Prikey === null) {
            return [null, new Error("Invalid Prikey")]
        }
        // var p = new sm2.Sm2PrivateKey(this.Prikey.PrivateKey)   // 存在问题！！
        return this.Prikey.Decrypt(msg, opts)
    }

    //
    // 使用Identify进行公钥加密
    //
    // @Description:
    // @receiver id
    // @param rand  io.Reader
    // @param data  []byte
    // @param opts  sec.DecrypterOpts
    // @return []byte
    // @return error
    //
    AsymEncrypt(data, opts) {
        if (this.Pubkey === null) {
            return [null, new Error("Invalid Pubkey")]
        }
        // var p = new sec.PublicKey(this.Pubkey.PublicKey)
        return this.Pubkey.Encrypt(data, opts)
    }

    //
    // 将Identity对象导出到字节数组
    //
    // @Description:
    // @receiver id
    // @param passwd  string
    // @return []byte
    // @return error
    //
    Dump(passwd) {
        var inner = parseIdentityToInner(this)
        if (inner[1] != null) {
            return [null, inner[1]]
        }
        console.log("inneridentity is", inner[0])
        var b
        try{
            b = JSON.stringify(inner[0])
            console.log("dump jsonMashal result is", b)
        }catch(err){
            return [null, err.message]
        }

        let ret 
        if (passwd.length > 0) {
            let hash = new hashfun.Hash(hashfun.HashType.SM3)
            let hashfunction = hash.New()
            let passHash = hashfunction(passwd)
            let passHashBytes = uti.Str2Bytes(passHash)
            if (passHashBytes.length == 64) {
                for (var i = 0; i < 32; i++) {
                    passHashBytes[i] = passHashBytes[i+32] + passHashBytes[i]
                }
            }
            try{
                console.log("passHashStr is", uti.Bytes2Str(passHashBytes).substring(0,31))
                ret = sm4.encrypt(b, uti.Bytes2Str(passHashBytes).substring(0,31)); 
            }catch(err){
                return [null, err.message]
            }
            // ret, err = sm4.Sm4Cbc(passHash[:16], b, sm4.ENC)
            console.log("dump encrypt is", ret)
        } else {
            console.log("passwd.length = 0")
            ret = b
        }
        console.log("uti.Str2Bytes(ret) is", uti.Str2Bytes(new Buffer.from(ret).toString('base64')))
        var dump_base64
        try{
            dump_base64 = [uti.Str2Bytes(new Buffer.from(ret).toString('base64')), null]
        }catch(err){
            return [null, err.message]
        }
        return dump_base64  
    }

    //
    // 从字节数组加载Identity对象
    //
    // @Description:
    // @receiver id
    // @param data   []byte
    // @param passwd   string
    // @return error
    //
    Load(data, passwd) {
        var idByte
        try{
            idByte = new Buffer.from(uti.Bytes2Str(data), 'base64').toString()
        }catch(err){
            return err
        }
        console.log("idBytes is", idByte)
        var plaintext 
        if (passwd.length > 0) {
            let hash = new hashfun.Hash(hashfun.HashType.SM3)
            let hashfunction = hash.New()
            let passHash = hashfunction(passwd)
            let passHashBytes = uti.Str2Bytes(passHash)
            if (passHashBytes.length == 64) {
                for (var i = 0; i < 32; i++) {
                    passHashBytes[i] = passHashBytes[i+32] + passHashBytes[i]
                }
            }
            try{
                plaintext = sm4.decrypt(idByte, uti.Bytes2Str(passHashBytes).substring(0,31));
            }catch(err){
                return err.message
            }
        } else {
            plaintext = idByte
        }
        console.log("the load plaintext is ", plaintext)

        var inner = new innerIdentity
        var res
        try{
            res = JSON.parse(plaintext, inner)
        }catch(err){
            return err.message
        }
        console.log("inner json parse is", res)

        var err = parseInnerToIdentity(res, this)
        if (err != null) {
            return err
        }

        return null
    }
    
    //
    // @Description:
    // @receiver id
    // @param passwd string
    // @param algo com.SymmetricAlgorithm
    // @return bool, error
    //
    Lock(passwd, algo) {
        switch (algo) {
        case com.SymmetricAlgorithm.SM4ECB:
            if (passwd.length > 0) {
                let hash = new sec.Hash(sec.HashType.SM3)
                let hashFunc = hash.New()
                let passHash = hashFunc(passwd)
                let passHashBytes = uti.Str2Bytes(passHash)
                if (passHashBytes.length != 64) {
                    return [false, "Invalid passwd length"]
                } else {
                    for (var i = 0; i < 32; i++) {
                        passHashBytes[i] = passHashBytes[i+32] + passHashBytes[i]
                    }
                }
            }
            let res = uti.MarshalPrivateKey(this.Prikey)
            console.log("MarshalPrivateKey is", res[1])
            this.PrikeyRawByte = res[1]
            this.Prikey = null
            return [true, null]
        }
        return [false, new Error("Unspported algorithm")]
    }

    //
    // @Description:
    // @receiver id
    // @return bool
    //
    IsLocked() {
        if (this.PrikeyRawByte.length > 0){  // 字节数组长度
            return true
        }
        return false
    }

    //
    // @Description:
    // @receiver id
    // @param passwd string
    // @param algo sec.SymmetricAlgorithm
    // @return bool, error
    //
    UnLock(passwd, algo) {
        if(this.Prikey != null){
            return [true, null]
        }
        switch (algo) {
        case com.SymmetricAlgorithm.SM4ECB:
            if (passwd.length === null || passwd === "") {
                return [false, "Invalid passwd length"]
            }
            let hash = new sec.Hash(sec.HashType.SM3)
            let hashFunc = hash.New()
            let passHash = hashFunc(passwd)
            let passHashBytes = uti.Str2Bytes(passHash)
            if (passHashBytes.length != 64) {
                return [false, "Invalid passwd length"]
            }
            var res = uti.UnMarshalPrivateKey(this.PrikeyRawByte, this.KeyParam.PublicKeyAlgorithm)
            if (res[1] != null) {
                return [false, res[1]]
            }
            this.Prikey = res[0]
            this.PrikeyRawByte = null
            return [true, null]
        }
        return [false, new Error("Unspported algorithm")]
    }

    //
    // 判断当前网络身份是否包含私钥
    //
    // @Description:
    // @receiver id
    // @return bool
    //
    HashPrivateKey() {
        if(this.PrikeyRawByte != null && this.PrikeyRawByte != undefined){
            return true
        }
        return this.Prikey != null
    }

}

String.prototype.format= function(){
    //将arguments转化为数组（ES5中并非严格的数组）
    var args = Array.prototype.slice.call(arguments)
    var count=0;
    //通过正则替换%s   
    return this.replace(/%s/g,function(s,i){
    return args[count++]
    })
}


// func (i *Identity) String() string {
// 	return fmt.Sprintf("{Name:%v,Passwd:%v}", i.Name, i.Passwd)
// }

//内部转码用的结构体
class innerIdentity {
    constructor(Name, KeyParam, Prikey, PubKey, Passwd, Cert){
        this.Name = Name     
        this.KeyParam = KeyParam
        this.Prikey = Prikey 
        this.Pubkey = PubKey   
        this.Passwd = Passwd   
        this.Cert = Cert     
    }
	// Name     string
	// KeyParam KeyParam
	// Prikey   []byte
	// Pubkey   []byte
	// Passwd   string
	// Cert     string
}

//
// 身份结构体转换为内部身份结构体
//
// @Description:
// @param id Identity
// @return innerIdentity
// @return error
//
function parseIdentityToInner(id) {
	let inner = new innerIdentity
	inner.Name = id.Name
	inner.KeyParam = id.KeyParam
	if (id.Prikey != null) {
        console.log("Prikey Bytes = ", id.Prikey.GetBytes())
		inner.Prikey = id.Prikey.GetBytes()
	}
	if (id.Pubkey != null) {
        console.log("Pubkey Bytes = ", id.Pubkey.GetBytes())
		inner.Pubkey = id.Pubkey.GetBytes()
	}
	inner.Passwd = id.Passwd
	var res
	res = id.Cert.ToPem(uti.Str2Bytes(id.Passwd), com.SymmetricAlgorithm.SM4ECB)
	// inner.Cert, err = id.Cert.ToPem([]byte(id.Passwd), sec.SM4CBC)
	if (res[1] != null) {
		return res
	}
    inner.Cert = res[0]
	return [inner, null]
}

//
// 内部身份结构体转换为身份结构体
//
// @Description:
// @param inner *innerIdentity
// @param id *Identity
// @return error
//
function parseInnerToIdentity(inner, id) {
    const uti = require("../crypto/utils")
	id.Name = inner.Name
    console.log("id.Name is ", id.Name)
	id.KeyParam = inner.KeyParam
	if (inner.Pubkey != null) {
		var res
		res = uti.UnMarshalPublicKey(inner.Pubkey, inner.KeyParam.PublicKeyAlgorithm)
        id.Pubkey = res[0]
		if (res[1] != null) {
			return res[1]
		}
	}

	if (inner.Prikey != null) {
		var res
		res = uti.UnMarshalPrivateKey(inner.Prikey, inner.KeyParam.PublicKeyAlgorithm)
        id.Prikey = res[0]
		if (res[1] != null) {
			return res[1]
		}
	}
	id.Passwd = inner.Passwd
    console.log("inner.Passwd is ", inner.Passwd)
    // console.log("id.Cert is ", id.Cert)
    id.Cert = new cer.Certificate()
	let err = FromPem(id.Cert, inner.Cert, uti.Str2Bytes(inner.Passwd), com.SymmetricAlgorithm.SM4ECB)
    // err := id.Cert.FromPem(inner.Cert, []byte(inner.Passwd), sec.SM4CBC)
	if (err != null) {
		return err
	}

	return null
}

// pem转换为证书结构体
//
// @Description:
// @param Cert1 *Certificate
// @param pemStr string
// @param passwd []byte
// @param mode com.SymmetricAlgorithm
// @return error
//
function FromPem(Cert1, pemStr, passwd, mode) {
    if (pemStr === "") {
        return new Error("Wrong certificate pem")
    }
    var pemByte
    try{
		pemByte = uti.Str2Bytes(new Buffer.from(pemStr, 'base64').toString("hex"))
    }catch(err){
        return err
    }
    var decCert 
    if (passwd != null && uti.Bytes2Str(passwd).length > 0) {
        let hash = new sec.Hash(sec.HashType.SM3)
        let hashFunc = hash.New()
        let passHash = hashFunc(uti.Bytes2Str(passwd))
        let passHashBytes = uti.Str2Bytes(passHash)
        if (passHashBytes.length == 64) {
            for (var i = 0; i < 32; i++) {
                passHashBytes[i] = passHashBytes[i+32] + passHashBytes[i]
            }
        }
        switch (mode) {
        case com.SymmetricAlgorithm.SM4ECB:
            console.log("The pemByte is", pemByte)
            try{
                console.log("passHashStr is", uti.Bytes2Str(passHashBytes).substring(0,31))
                decCert = sm4.decrypt(uti.Bytes2Str(pemByte), uti.Bytes2Str(passHashBytes).substring(0,31));
            }catch(err){
                return err.message
            }
            break;
        case com.SymmetricAlgorithm.SM4CBC:
            // TO DO
            // decCert, err = sm4.Sm4Cbc(passHash[:16], pemByte, sm4.DEC)
            // if err != nil {
            // 	return err
            // }
        default:
            return new Error("Unknown SymmetricAlgorithm")
        }
    } else {
        console.log("passwd为空")
        decCert = uti.Bytes2Str(pemByte)
    }

    let c = new cer.certificate
    console.log("decCert is", decCert)
    try{
        c = JSON.parse(decCert)
        console.log("json解析后：", c)
        console.log("json解析后PublicKey情况", c.TBSCertificate.PublicKey)
        //_, err = asn1.Unmarshal(decCert, c)
        // if err != nil {
        // 	return errors.New("wrong password")
        // }
        console.log("cert.TBSCertificate.Version", c.TBSCertificate.Version)
    }catch(err){
        return err.message
    }
    let err = cer.parseInnerCertToCert(c, Cert1)
    if (err != null) {
        return err
    }
    return null
}

//
// @Description: 创建身份
// @param name string
// @param param KeyParam
// @return Identity
//
function CreateIdentity(name, param) {
	var id = new Identity
	id.Name = name
	switch (param.PublicKeyAlgorithm) {
	case com.PublicKeyAlgorithm.SM2:
        let keypair = sm2.GenKeyPair()
        let pri = keypair[0]
        let pub = keypair[1]
        id.Prikey = new sec.PrivateKey(pri.PrivateKey)
        id.Pubkey = new sec.PublicKey(pub.PublicKey)
	}
	return id
}

module.exports = {
    Identity: Identity,
    KeyParam: KeyParam,
	innerIdentity: innerIdentity,
    parseIdentityToInner: parseIdentityToInner,
	parseInnerToIdentity: parseInnerToIdentity,
    CreateIdentity: CreateIdentity
}
